#include "rpc_protocol.h"
#include <stdio.h>

/**
 * @brief Helper function to convert a UserProfile struct to a cJSON object.
 * MISRA: Declared as static to limit its scope to this file (Rule 8.7).
 * @param profile Pointer to the UserProfile struct.
 * @return A cJSON object representing the struct, or NULL on failure.
 */
static cJSON *user_profile_to_json(const UserProfile *profile)
{
  if (profile == NULL)
  {
    return NULL;
  }
  cJSON *json_obj = cJSON_CreateObject();
  if (json_obj == NULL)
  {
    return NULL;
  }

  // MISRA: Check the return value of each cJSON function that can fail.
  if (cJSON_AddStringToObject(json_obj, "name", profile->name) == NULL)
  {
    cJSON_Delete(json_obj);
    return NULL;
  }
  if (cJSON_AddNumberToObject(json_obj, "age", profile->age) == NULL)
  {
    cJSON_Delete(json_obj);
    return NULL;
  }
  if (cJSON_AddStringToObject(json_obj, "city", profile->city) == NULL)
  {
    cJSON_Delete(json_obj);
    return NULL;
  }
  return json_obj;
}

char *create_json_rpc_request(const char *method_name, int32_t request_id,
                              const char *param_format, ...)
{
  cJSON *request = cJSON_CreateObject();
  if (request == NULL)
  {
    return NULL;
  }

  cJSON_AddStringToObject(request, "jsonrpc", RPC_JSON_VERSION);
  cJSON_AddStringToObject(request, "method", method_name);

  cJSON *params = cJSON_CreateArray();
  if (params == NULL)
  {
    cJSON_Delete(request);
    return NULL;
  }
  cJSON_AddItemToObject(request, "params", params);

  va_list args;
  va_start(args, param_format);

  for (const char *p = param_format; *p != '\0'; ++p)
  {
    cJSON *item = NULL;
    switch (*p)
    {
    case 'i':
    {
      int32_t i = va_arg(args, int32_t);
      item = cJSON_CreateNumber(i);
      break;
    }
    case 'd':
    {
      double d = va_arg(args, double);
      item = cJSON_CreateNumber(d);
      break;
    }
    case 's':
    {
      const char *s = va_arg(args, const char *);
      item = cJSON_CreateString(s);
      break;
    }
    case 'S':
    {
      const UserProfile *profile = va_arg(args, const UserProfile *);
      item = user_profile_to_json(profile);
      break;
    }
    default:
      // MISRA: A switch statement should have a default case (Rule 16.4).
      fprintf(stderr, "Warning: Unknown parameter format character '%c'\n", *p);
      break;
    }
    if (item != NULL)
    {
      cJSON_AddItemToArray(params, item);
    }
  }

  va_end(args);

  cJSON_AddNumberToObject(request, "id", request_id);

  char *request_str = cJSON_PrintUnformatted(request);
  cJSON_Delete(request);
  return request_str;
}

cJSON *parse_json_rpc_response(const char *response_str, int32_t expected_id)
{
  cJSON *response_json = cJSON_Parse(response_str);
  if (response_json == NULL)
  {
    fprintf(stderr, "Error: Failed to parse JSON response.\n");
    return NULL;
  }

  cJSON *id_json = cJSON_GetObjectItem(response_json, "id");
  if (!cJSON_IsNumber(id_json) || (int32_t)id_json->valuedouble != expected_id)
  {
    fprintf(stderr, "Error: Response ID mismatch or missing.\n");
    cJSON_Delete(response_json);
    return NULL;
  }

  cJSON *error_json = cJSON_GetObjectItem(response_json, "error");
  if (error_json != NULL)
  {
    cJSON *msg = cJSON_GetObjectItem(error_json, "message");
    if (cJSON_IsString(msg))
    {
      fprintf(stderr, "RPC Error received: %s\n", msg->valuestring);
    }
    // Return the full JSON object so the caller can inspect the error.
    return response_json;
  }

  return response_json;
}

char *create_json_rpc_success_response(cJSON *result, cJSON *id)
{
  cJSON *response = cJSON_CreateObject();
  cJSON_AddStringToObject(response, "jsonrpc", RPC_JSON_VERSION);
  // The 'result' object's ownership is transferred to the 'response' object.
  cJSON_AddItemToObject(response, "result", result);
  // Add a reference to the ID, do not take ownership.
  cJSON_AddItemReferenceToObject(response, "id", id);

  char *response_str = cJSON_PrintUnformatted(response);
  cJSON_Delete(response);
  return response_str;
}

char *create_json_rpc_error_response(int32_t code, const char *message,
                                     cJSON *id)
{
  cJSON *response = cJSON_CreateObject();
  cJSON *error_obj = cJSON_CreateObject();

  cJSON_AddNumberToObject(error_obj, "code", code);
  cJSON_AddStringToObject(error_obj, "message", message);

  cJSON_AddStringToObject(response, "jsonrpc", RPC_JSON_VERSION);
  cJSON_AddItemToObject(response, "error", error_obj);
  if (id != NULL && !cJSON_IsNull(id))
  {
    cJSON_AddItemReferenceToObject(response, "id", id);
  }
  else
  {
    cJSON_AddNullToObject(response, "id");
  }

  char *response_str = cJSON_PrintUnformatted(response);
  cJSON_Delete(response);
  return response_str;
}
